#include <strmif.h>
#include "cfilereader.hpp"
#include "cmemallocator.hpp"
#include <cassert>
#include <vfwmsgs.h>


CFileReader::CFileReader() :
    m_hFile(INVALID_HANDLE_VALUE)
{
}


CFileReader::~CFileReader()
{
    const HRESULT hr = Close();
    hr;
    assert(SUCCEEDED(hr));
}


HRESULT CFileReader::SetPosition(LONGLONG pos) const
{
    const BOOL b = SetFilePointerEx(
                    m_hFile, 
                    reinterpret_cast<LARGE_INTEGER&>(pos), 
                    0,
                    FILE_BEGIN);  //TODO: generalize
    
    if (b)
        return S_OK;
        
    const DWORD e = GetLastError();
    return HRESULT_FROM_WIN32(e);
}


HRESULT CFileReader::Open(const wchar_t* strFileName)
{
    if (strFileName == 0)
        return E_INVALIDARG;
        
    if (m_hFile != INVALID_HANDLE_VALUE)
        return E_UNEXPECTED;
        
    m_hFile = CreateFile(
                strFileName,
                GENERIC_READ,
                FILE_SHARE_READ,
                0,  //security attributes
                OPEN_EXISTING,
                FILE_ATTRIBUTE_READONLY,
                0);
                
    if (m_hFile == INVALID_HANDLE_VALUE)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
        
    LARGE_INTEGER size;
    
    const BOOL b = GetFileSizeEx(m_hFile, &size);
    
    if (!b)
    {
        const DWORD e = GetLastError();
        Close();
        return HRESULT_FROM_WIN32(e);
    }
    
    m_length = size.QuadPart;
    assert(m_length >= 0);
    
    return S_OK;
}


HRESULT CFileReader::Close()
{
    if (m_hFile == INVALID_HANDLE_VALUE)
        return S_FALSE;
        
    const BOOL b = CloseHandle(m_hFile);
    
    m_hFile = INVALID_HANDLE_VALUE;
    
    if (b)
        return S_OK;
        
    const DWORD e = GetLastError();
    return HRESULT_FROM_WIN32(e);
}


bool CFileReader::IsOpen() const
{
    return (m_hFile != INVALID_HANDLE_VALUE);
}


HRESULT CFileReader::QueryInterface(const IID& iid, void** ppv)
{
    if (ppv == 0)
        return E_INVALIDARG;
        
    IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
    
    if (iid == __uuidof(IUnknown))
        pUnk = static_cast<IUnknown*>(this);
        
    else if (iid == __uuidof(IAsyncReader))
        pUnk = static_cast<IAsyncReader*>(this);
        
    else
    {
        pUnk = 0;
        return E_NOINTERFACE;
    }
    
    pUnk->AddRef();
    return S_OK;
}


ULONG CFileReader::AddRef()
{
    return 2;
}


ULONG CFileReader::Release()
{
    return 1;
}


HRESULT CFileReader::RequestAllocator( 
    IMemAllocator* pPreferred,
    ALLOCATOR_PROPERTIES* pProps,
    IMemAllocator** ppActual)
{
    if (!IsOpen())
        return E_UNEXPECTED;

    if (pPreferred == 0)
    {
        const HRESULT hr = CMemAllocator::CreateInstance(ppActual);
        
        if (FAILED(hr))
            return hr;
    }
    else
    {
        if (ppActual == 0)
            return E_POINTER;

        IMemAllocator*& pActual = *ppActual;
        
        pActual = pPreferred;
        pActual->AddRef();
    }
    
    ALLOCATOR_PROPERTIES props;
    
    if (pProps == 0)  //weird
    {
        props.cBuffers = 0;
        props.cbBuffer = 0;
        props.cbAlign = 1;
        props.cbPrefix = 0;
    }
    else
    {
        props = *pProps;
        
        if (props.cBuffers < 0)
            props.cBuffers = 0;
            
        if (props.cbBuffer < 0)
            props.cbBuffer = 0;
            
        if (props.cbAlign <= 0)
            props.cbAlign = 1;
            
        if (props.cbPrefix < 0)
            props.cbPrefix = 0;
    }
        
    IMemAllocator*& pActual = *ppActual;

    const HRESULT hr = pActual->SetProperties(&props, &m_props);
    
    if (SUCCEEDED(hr))
        return hr;
        
    pActual->Release();
    pActual = 0;
    
    return hr;
}


HRESULT CFileReader::Request( 
    IMediaSample* pSample,
    DWORD_PTR user)
{
    if (pSample == 0)
        return E_INVALIDARG;
        
    __int64 st, sp;
    
    HRESULT hr = pSample->GetTime(&st, &sp);
    
    if (hr != S_OK)  //need both start and stop
        return VFW_E_SAMPLE_TIME_NOT_SET;
        
    if (st % 10000000)
        return E_INVALIDARG;  //?
        
    if (sp % 10000000)
        return E_INVALIDARG;  //?
        
    const __int64 start = st / 10000000;
    
    const long size = pSample->GetSize();
    
    if (size <= 0)
        return E_INVALIDARG;
        
    if (!IsOpen())
        return E_UNEXPECTED;
    
    if (m_bFlush)
        return VFW_E_WRONG_STATE;
        
    //TODO: check alignment
    
    if (start >= m_length)
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
        
    //TODO: check for duplicate key
        
    m_read_queue.push_back(ReadInfo(pSample, user));
    
    return S_OK;
}
    
    
HRESULT CFileReader::WaitForNext( 
    DWORD /* timeout */ ,
    IMediaSample** ppSample,
    DWORD_PTR* pUser)
{
    if (ppSample == 0)
        return E_POINTER;
        
    IMediaSample*& pSample = *ppSample;
    pSample = 0;
        
    if (!IsOpen())
        return E_UNEXPECTED;

    if (m_bFlush)
    {
        if (!m_read_queue.empty())
        {
            ReadInfo& r = m_read_queue.front();
            
            pSample = r.m_pSample;
            assert(pSample);
            
            const HRESULT hr = pSample->SetActualDataLength(0);
            hr;
            
            if (pUser)
                *pUser = r.m_user;
                
            m_read_queue.pop_front();
        }
                
        return VFW_E_WRONG_STATE;
    }
    
    if (m_read_queue.empty())
    {
        if (pUser)
            *pUser = 0;
            
        return S_OK;  //TODO: is this correct?
    }
    
    {
        ReadInfo& r = m_read_queue.front();
        
        pSample = r.m_pSample;
        assert(pSample);
        
        if (pUser)
            *pUser = r.m_user;

        m_read_queue.pop_front();
    }        
    
    __int64 st, sp;
    
    HRESULT hr = pSample->GetTime(&st, &sp);
    assert(hr == S_OK);
    assert(st % 10000000 == 0);
    assert(sp % 10000000 == 0);

    const __int64 start = st / 10000000;
    assert(start < m_length);  //TODO: allow this?
    
    const __int64 stop = sp / 10000000;
    
    const __int64 requested_len = stop - start + 1;
    
    if (requested_len <= 0)
    {
        pSample->SetActualDataLength(0);
        return S_OK;
    }
    
    const long size = pSample->GetSize();
    assert(size > 0);
    
    BYTE* ptr;
    
    hr = pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    DWORD n;
    
    if (requested_len <= size)
        n = static_cast<DWORD>(requested_len);
    else
        n = size;

    hr = SetPosition(start);
    assert(SUCCEEDED(hr));

    DWORD m;
        
    const BOOL b = ReadFile(m_hFile, ptr, n, &m, 0);
    
    if (!b)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
        
    pSample->SetActualDataLength(m);
        
    if (m < n)
        return S_FALSE;
        
    if (requested_len <= size)
        return S_OK;
        
    //TODO: decide whether this case should be allowed
    //I don't quite understand the aligned I/O yet.
        
    return S_FALSE;
}
    


HRESULT CFileReader::SyncReadAligned(IMediaSample* pSample)
{
    if (pSample == 0)
        return E_INVALIDARG;
        
    __int64 st, sp;
    
    HRESULT hr = pSample->GetTime(&st, &sp);
    
    if (hr != S_OK)  //need both start and stop
        return VFW_E_SAMPLE_TIME_NOT_SET;
        
    if (st % 10000000)
        return E_INVALIDARG;  //?
        
    if (sp % 10000000)
        return E_INVALIDARG;  //?
        
    const __int64 start = st / 10000000;
    
    const long size = pSample->GetSize();
    
    if (size <= 0)
        return E_INVALIDARG;

    if (!IsOpen())
        return E_UNEXPECTED;
    
    //TODO: need to check this here?
    //if (m_bFlush)
    //    return VFW_E_WRONG_STATE;
        
    //TODO: check alignment
    
    if (start >= m_length)
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
        
    const __int64 stop = sp / 10000000;
    
    const __int64 requested_len = stop - start + 1;
    
    if (requested_len <= 0)
    {
        pSample->SetActualDataLength(0);
        return S_OK;
    }
    
    BYTE* ptr;
    
    hr = pSample->GetPointer(&ptr);
    assert(SUCCEEDED(hr));
    assert(ptr);
    
    DWORD n;
    
    if (requested_len <= size)
        n = static_cast<DWORD>(requested_len);
    else
        n = size;
    
    hr = SetPosition(start);
    assert(SUCCEEDED(hr));

    DWORD m;
        
    const BOOL b = ReadFile(m_hFile, ptr, n, &m, 0);
    
    if (!b)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }

    pSample->SetActualDataLength(m);
            
    if (m < n)
        return S_FALSE;
        
    if (requested_len <= size)
        return S_OK;

    //TODO: decide whether this case should be allowed
    //I don't quite understand the aligned I/O yet.

    return S_FALSE;
}

    
HRESULT CFileReader::SyncRead( 
    LONGLONG start,
    LONG len,
    BYTE* ptr)
{
    if (start < 0)
        return E_INVALIDARG;
        
    if (len <= 0)
        return S_OK;        
    
    if (!IsOpen())
        return E_UNEXPECTED;
    
    //TODO: need to check this here?
    //if (m_bFlush)
    //    return VFW_E_WRONG_STATE;
    
    if (start >= m_length)
        return HRESULT_FROM_WIN32(ERROR_HANDLE_EOF);
        
    const HRESULT hr = SetPosition(start);
    assert(SUCCEEDED(hr));

    DWORD m;
        
    const BOOL b = ReadFile(m_hFile, ptr, len, &m, 0);
    
    if (!b)
    {
        const DWORD e = GetLastError();
        return HRESULT_FROM_WIN32(e);
    }
        
    return (m >= ULONG(len)) ? S_OK : S_FALSE;
}


    
HRESULT CFileReader::Length( 
    LONGLONG* pTotal,
    LONGLONG* pAvailable)
{
    if (!IsOpen())
        return E_UNEXPECTED;
        
    if (pTotal)
        *pTotal = m_length;
        
    if (pAvailable)
        *pAvailable = m_length;
                
    return S_OK;
}


HRESULT CFileReader::BeginFlush()
{
    m_bFlush = true;    
    return S_OK;
}


HRESULT CFileReader::EndFlush()
{
    m_bFlush = false;    
    return S_OK;
}
